/**
 * 
 */
package com.rytong.tools.crypto;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.rytong.tools.utils.Utils;

/**
 * @author Chris
 * 
 */
public final class HMac {

    /**
     * MAC算法可选以下多种算法
     * 
     * <pre>
     * HmacMD5
     * HmacSHA1
     * </pre>
     */
    public static final String KEY_MAC_MD5 = "HmacMD5";
    public static final String KEY_MAC_SHA1 = "HmacSHA1";

    /**
     * HMAC encrypt
     * 
     * @param data
     * @param key
     * @param keyMacMode
     * @return
     * @throws Exception
     */
    public static String encryptHMAC(String data, byte[] key, String keyMacMode) throws Exception {
        byte[] dataByt = data.getBytes("UTF-8");
        byte[] hmacByt = encryptHMAC(dataByt, key, keyMacMode);
        String hmacStr = Base64.encode(hmacByt);
        return hmacStr;
    }

    /**
     * @param data
     * @param key
     * @param keyMacMode
     * @return
     * @throws Exception
     */
    public static byte[] encryptHMAC(byte[] data, byte[] key, String keyMacMode) throws Exception {
        SecretKey secretKey = new SecretKeySpec(key, keyMacMode);
        Mac mac = Mac.getInstance(secretKey.getAlgorithm());
        mac.init(secretKey);
        byte[] hmacByt = mac.doFinal(data);
        return hmacByt;
    }

    /**
     * @param data
     * @return
     */
    public static byte[] MD5(byte[] data) {
        // try {
        // MessageDigest md5 = MessageDigest.getInstance("MD5");
        // md5.update(data);
        // return md5.digest();
        // } catch (NoSuchAlgorithmException e) {
        // LPUtils.printException(e);
        // return null;
        // }
        try {
            return MD5.getMD5(data);
        } catch (Exception e) {
            Utils.printException(e);
            return null;
        }
    }

    /**
     * @param data
     * @return
     */
    public static byte[] SHA1(byte[] data) {
        try {
            MessageDigest sha1 = MessageDigest.getInstance("SHA1");
            sha1.update(data);
            return sha1.digest();
        } catch (NoSuchAlgorithmException e) {
            Utils.printException(e);
        }
        return null;
    }

    // 将字节数组转换为十六进制字符串
    public static String byteArrayToHexString(byte[] bytearray) {
        String strDigest = "";
        for (int i = 0; i < bytearray.length; i++) {
            strDigest += byteToHexString(bytearray[i]);
        }
        return strDigest;
    }

    // sha1加密(由字节数组转换为字符串)
    // 将字节转换为十六进制字符串
    private static String byteToHexString(byte ib) {
        char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
        char[] ob = new char[2];
        ob[0] = Digit[(ib >>> 4) & 0X0F];
        ob[1] = Digit[ib & 0X0F];
        String s = new String(ob);
        return s;
    }

    public static void main(String[] args) {

        try {
            byte[] secret = new byte[] { 1, 2, 3, 4, 5, 6 };
            byte[] label = null;
            label = "master secret".getBytes("UTF-8");
            byte[] seed = new byte[] { 7, 8, 9, 10, 11, 12, 13, 14, 15 };
            byte[] prf = PRF(secret, label, seed, 80);
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            Utils.printException(e1);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            Utils.printException(e);
        }

        String key = "lightpole";
        String data = "www.rytong.net";

        try {
            byte[] keyByt = key.getBytes("UTF-8");
            String md5Str = HMac.encryptHMAC(data, keyByt, KEY_MAC_MD5);
            Utils.printOutToConsole(md5Str);
            String sha1Str = HMac.encryptHMAC(data, keyByt, KEY_MAC_SHA1);
            Utils.printOutToConsole(sha1Str);
        } catch (Exception e) {
            Utils.printException(e);
        }

        try {
            String storepass = "123456";
            // 前面设置的密码
            String keystorePath = "e:\\license\\a\\chinajavaworld.keystore";
            FileInputStream in = new FileInputStream(keystorePath);
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(in, storepass.toCharArray());
            // 获取证书
            Certificate c1 = ks.getCertificate("chinajavaworld");
            // BASE64编码
            // System.out.println(StringUtils.encodeBase64(c1.getEncoded()));
            // 将chinajavaworld.cer内容改为这里输出的内容

            Signature signature;
            // 从密钥库中读取CA证书
            String cerPath = "e:\\license\\a\\test.cer";
            InputStream streamCert = new java.io.FileInputStream(cerPath);
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            Certificate cert = factory.generateCertificate(streamCert);
            // System.out.println(StringUtils.encodeBase64(cert.getEncoded()));
            signature = Signature.getInstance("SHA1withDSA");
            signature.initVerify(cert.getPublicKey());
            // 要签名的指纹内容
            String sss = "Welcome to www.chinajavaworld.com,The java world for you forever.";
            // 获取CA证书私钥
            PrivateKey priKey = (PrivateKey) ks.getKey("test", storepass.toCharArray());
            // 　　System.out.println("priKey:"+StringUtils.encodeHex(priKey.getEncoded()));
            // 用私钥签名
            Signature sig = Signature.getInstance("SHA1withDSA");
            sig.initSign(priKey);
            ByteArrayOutputStream streamRaw0 = new ByteArrayOutputStream();
            DataOutputStream streamSig0 = new DataOutputStream(streamRaw0);
            streamSig0.writeUTF(sss);
            sig.update(streamRaw0.toByteArray());
            // String signatureS = StringUtils.encodeHex(sig.sign());
            // System.out.println("signature: "+signatureS);
            // 用公钥做验证测试
            // System.out.println("pubKey:"+StringUtils.encodeHex(cert.getPublicKey().getEncoded()));
            ByteArrayOutputStream streamRaw = new ByteArrayOutputStream();
            DataOutputStream streamSig = new DataOutputStream(streamRaw);
            streamSig.writeUTF(sss);
            signature.update(streamRaw.toByteArray());
            // System.out.println("verify: "+signature.verify(StringUtils.decodeHex(signatureS)));
        } catch (Exception e) {
            Utils.printException(e);
        }

    }

    /**
     * PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed)
     * 
     * @param secret
     * @param label
     * @param seed
     * @param length
     * @return
     */
    public static byte[] PRF(byte[] secret, byte[] label, byte[] seed, int length) throws Exception {
        int secretLen = secret.length;
        boolean is2Times = (secretLen % 2) == 0 ? true : false;
        int splittingLen = secretLen / 2 + (is2Times ? 0 : 1);
        byte[] s1 = new byte[splittingLen];
        byte[] s2 = new byte[splittingLen];
        System.arraycopy(secret, 0, s1, 0, splittingLen);
        System.arraycopy(secret, splittingLen, s2, 0, splittingLen);
        // label + seed
        byte[] labelAndSeed = new byte[label.length + seed.length];
        System.arraycopy(label, 0, labelAndSeed, 0, label.length);
        System.arraycopy(seed, 0, labelAndSeed, label.length, seed.length);
        // PRF MD5
        byte[] prfMd5 = prfHash(s1, labelAndSeed, length, HMac.KEY_MAC_MD5);
        // PRF SHA1
        byte[] prfSha1 = prfHash(s2, labelAndSeed, length, HMac.KEY_MAC_SHA1);
        // PRF
        byte[] prf = xor(prfMd5, prfSha1);
        return prf;
    }

    /**
     * @param s1
     * @param labelAndSeed
     * @param length
     * @param keyMacMode
     * @return
     * @throws Exception
     */
    private static byte[] prfHash(byte[] s1, byte[] labelAndSeed, int length, String keyMacMode) throws Exception {
        ArrayList<byte[]> A = new ArrayList<byte[]>();
        A.add(labelAndSeed);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        /*
         * P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + HMAC_hash(secret, A(2) + seed) + HMAC_hash(secret,
         * A(3) + seed) + ...
         */
        while (out.size() < length) {
            int arrSize = A.size();
            byte[] lastA = A.get(arrSize - 1);
            // A(i) = HMAC_hash(secret, A(i-1))
            byte[] currA = encryptHMAC(lastA, s1, keyMacMode);
            A.add(currA);
            byte[] neoSeed = jogBytes(currA, labelAndSeed);
            byte[] byts = encryptHMAC(neoSeed, s1, keyMacMode);
            out.write(byts);
        }
        out.flush();
        byte[] outByts = out.toByteArray();
        byte[] prfMd5 = new byte[length];
        System.arraycopy(outByts, 0, prfMd5, 0, prfMd5.length);
        return prfMd5;
    }

    /**
     * @param firstBytes
     * @param lastBytes
     * @return
     */
    public static byte[] jogBytes(byte[]... byts) throws Exception {
        if (byts.length == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for (byte[] byt : byts) {
            if (byt == null) {
                continue;
            }
            out.write(byt);
        }
        try {
            out.flush();
        } catch (Exception e) {
            Utils.printException(e);
        }
        byte[] result = out.toByteArray();
        try {
            out.close();
        } catch (Exception e) {
            Utils.printException(e);
        }
        out = null;
        return result;
    }

    /**
     * @param byts1
     * @param byts2
     * @return
     */
    private static byte[] xor(byte[] byts1, byte[] byts2) throws Exception {
        int len1 = byts1.length;
        int len2 = byts2.length;
        if (len1 != len2) {
            return null;
        }
        byte[] xor = new byte[len1];
        for (int i = 0; i < len1; i++) {
            xor[i] = (byte) (byts1[i] ^ byts2[i]);
        }
        return xor;
    }

    /**
     * 
     * @return
     */
    public static final byte[] TLS_MD_CLIENT_FINISH_CONST() {
        try {
            return "client finished".getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Utils.printException(e);
        }
        return null;
    }

    /**
     * 用于Finished
     * 
     * @return
     */
    public static final byte[] TLS_MD_SERVER_FINISH_CONST() {
        try {
            return "server finished".getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Utils.printException(e);
        }
        return null;
    }

    /**
     * 用于计算MS
     * 
     * @return
     */
    public static final byte[] TLS_MD_MASTER_SECRET_CONST() {
        try {
            return "master secret".getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Utils.printException(e);
        }
        return null;
    }

    /**
     * 用于计算MS2
     * 
     * @return
     */
    public static final byte[] TLS_MD_MASTER_SECRET2_CONST() {
        try {
            return "master secret2".getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Utils.printException(e);
        }
        return null;
    }

    /**
     * 生成PMS
     * 
     * @return
     */
    public static final byte[] TLS_MD_PREMASTER_SECRET_CONST() {
        try {
            return "premaster secret".getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Utils.printException(e);
        }
        return null;
    }

    /**
     * 生成PMS2
     * 
     * @return
     */
    public static final byte[] TLS_MD_PREMASTER_SECRET2_CONST() {
        try {
            return "premaster secret2".getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Utils.printException(e);
        }
        return null;
    }

    /**
     * 用于客户端与服务器PRF计算后输出最终密钥值（KEY、IV、MACKEY）
     * 
     * @return
     */
    public static final byte[] TLS_MD_CLIENT_SERVER_KEYIVMAC_CONST() {
        try {
            return "key expansion".getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Utils.printException(e);
        }
        return null;
    }

    public static final byte[] TLS_ONCE_SECRET_CONST() {
        try {
            return "once secret".getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Utils.printException(e);
        }
        return null;
    }
}
